package editorsvg.editor.documento;

import editorsvg.gui.generico.Actualizable;
import editorsvg.utilidades.Angulo;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 *
 * @author Jorge Berjano
 */
public class ListaElementos implements Actualizable, Cloneable {

    protected LinkedList<ElementoGrafico> lista = new LinkedList<ElementoGrafico>();
    private Area contorno;

    public String toString() {
        return lista.toString();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        ListaElementos clon = (ListaElementos) super.clone();
        clon.lista = (LinkedList<ElementoGrafico>) lista.clone();
        return clon;
    }

    @Override
    public void actualizar() {
        for (ElementoGrafico elemento : lista) {
            elemento.actualizar();
        }
        actualizarContorno();
    }

    public List<ElementoGrafico> getLista() {
        return lista;
    }

    public void asignarLista(ListaElementos listaElementos) {
        this.lista = listaElementos.lista;
    }

    public void agregar(ElementoGrafico elemento) {
        lista.addLast(elemento);
        actualizarContorno();
    }

    public void agregar(int indice, ElementoGrafico elemento) {
        lista.add(indice, elemento);
        actualizarContorno();
    }

    public void eliminar(ElementoGrafico elemento) {
        lista.remove(elemento);
        actualizarContorno();
    }

    public void limpiar() {
        lista.clear();
        actualizarContorno();
    }

    public boolean tieneElementos() {
        return lista.size() > 0;
    }

    public boolean contiene(ElementoGrafico elemento) {
        return lista.contains(elemento);
    }

    public Rectangle2D getEncuadre() {
        if (lista.size() == 0) {
            return null;
        }

        Rectangle2D encuadre = lista.get(0).getEncuadre();
        for (ElementoGrafico elemento : lista) {
            encuadre.add(elemento.getEncuadre());
        }
        return encuadre;
    }

    public Shape getContorno() {
        return contorno;
    }

    public void actualizarContorno() {

        contorno = new Area();
        for (ElementoGrafico elemento : lista) {
            Shape contornoElemento = elemento.getContorno();
            if (contornoElemento == null) {
//                System.err.println("fallo!");
            } else {
                contorno.add(new Area(contornoElemento));
            }
        }
    }

    public void pintar(Graphics2D graphics) {

        for (ElementoGrafico elemento : lista) {
            elemento.pintar(graphics);
//            System.out.println("pintar + " + elemento);
        }

//        graphics.setColor(Color.RED);
//        graphics.draw(getContorno());
    }

    /**
     * Recibe la visita de un visitante, visitando tambien todos sus elementos hasta 
     * que el valor retornado sea false.
     */ 
    public boolean recorrer(Visitante visitante) {
        
        for (ElementoGrafico elemento : lista) {            
            if (!elemento.recorrer(visitante)) {
                return false;
            }
        }
        return true;
    }
    
    
    /**
     * Itera en la lista de elementos haciendo que un visitante visite todos los elementos.
     * @param visitante El visitante.
     * @return False si se desea interrumpir la iteracción.
     */
    public boolean iterarDescendente(Visitante visitante) {
        return iterar(visitante, lista.descendingIterator());
    }

    public boolean iterarAscendente(Visitante visitante) {
        return iterar(visitante, lista.iterator());
    }    
    
    private boolean iterar(Visitante visitante, Iterator<ElementoGrafico> iterador) {
        while (iterador.hasNext()) {
            ElementoGrafico elemento = iterador.next();
            if (!visitante.visitar(elemento)) {
                return false;
            }
        }
        return true;
    }


//    public void bajar(NodoGrafico elementoGrafico) {
//        int indice = lista.indexOf(elementoGrafico);
//        
//        if (indice - 1 >= 0) {
//            lista.set(indice, lista.get(indice - 1));
//            lista.set(indice - 1, elementoGrafico);
//        }
//    }
//    public void subir(NodoGrafico elementoGrafico) {
//        int indice = lista.indexOf(elementoGrafico);
//        
//        if (indice + 1 < lista.size()) {
//            lista.set(indice, lista.get(indice + 1));
//            lista.set(indice + 1, elementoGrafico);
//        }
//    }
    public void traerAlFrente(ConjuntoElementos listaNodosASubir) {
        ListIterator<ElementoGrafico> iterador = lista.listIterator(0);
        List<ElementoGrafico> elementosSuperiores = new ArrayList<ElementoGrafico>(listaNodosASubir.getLista().size());

        while (iterador.hasNext()) {
            ElementoGrafico elemento = iterador.next();

            if (listaNodosASubir.contiene(elemento)) {
                iterador.remove();
                elementosSuperiores.add(elemento);
            }
        }
        lista.addAll(elementosSuperiores);
    }

    public void llevarAlFondo(ConjuntoElementos listaNodosABajar) {
        ListIterator<ElementoGrafico> iterador = lista.listIterator(0);
        List<ElementoGrafico> elementosInferiores = new ArrayList<ElementoGrafico>(listaNodosABajar.getLista().size());

        while (iterador.hasNext()) {
            ElementoGrafico elemento = iterador.next();

            if (listaNodosABajar.contiene(elemento)) {
                iterador.remove();
                elementosInferiores.add(elemento);
            }
        }
        lista.addAll(0, elementosInferiores);
    }

    public void subir(ConjuntoElementos listaNodosASubir) {
        ListIterator<ElementoGrafico> iterador = lista.listIterator(lista.size());
        ElementoGrafico elementoEncima = null;

        while (iterador.hasPrevious()) {
            ElementoGrafico elemento = iterador.previous();

            if (elementoEncima != null &&
                    listaNodosASubir.contiene(elemento) &&
                    !listaNodosASubir.contiene(elementoEncima)) {
                iterador.set(elementoEncima);
                iterador.next();
                iterador.next();
                iterador.set(elemento);
                iterador.previous();
                iterador.previous();
            } else {
                elementoEncima = elemento;
            }
        }
    }

    /**
     * Devuelve el subconjuto de elementos que pueden subir un nivel 
     * del conjunto de elementos que se especifica.
     * @param elementos El conjunto de elementos que se desea comprobar si pueden subir.
     * @return El conjunto de elementos que pueden subir un nivel
     */
    public ConjuntoElementos puedenSubir(ConjuntoElementos elementos) {
        ConjuntoElementos elementosASubir = new ConjuntoElementos();

        ListIterator<ElementoGrafico> iterador = lista.listIterator(lista.size());
        ElementoGrafico elementoEncima = null;
        while (iterador.hasPrevious()) {
            ElementoGrafico elemento = iterador.previous();

            if (elementoEncima != null &&
                    elementos.contiene(elemento) &&
                    !elementos.contiene(elementoEncima)) {
                elementosASubir.agregar(elemento);
            } else {
                elementoEncima = elemento;
            }
        }
        return elementosASubir;
    }

    /**
     * Devuelve el subconjuto de elementos que pueden bajar un nivel 
     * del conjunto de elementos que se especifica.
     * @param elementos El conjunto de elementos que se desea comprobar si pueden bajar.
     * @return El conjunto de elementos que pueden bajar un nivel
     */
    public ConjuntoElementos puedenBajar(ConjuntoElementos elementos) {
        ConjuntoElementos elementosABajar = new ConjuntoElementos();

        ListIterator<ElementoGrafico> iterador = lista.listIterator(0);
        ElementoGrafico elementoDebajo = null;
        while (iterador.hasNext()) {
            ElementoGrafico elemento = iterador.next();
            if (elementoDebajo != null &&
                    elementos.contiene(elemento) &&
                    !elementos.contiene(elementoDebajo)) {
                elementosABajar.agregar(elemento);
            } else {
                elementoDebajo = elemento;
            }
        }
        return elementosABajar;
    }

    public void bajar(ConjuntoElementos elementosABajar) {
        ListIterator<ElementoGrafico> iterador = lista.listIterator(0);
        ElementoGrafico elementoDebajo = null;
        while (iterador.hasNext()) {
            ElementoGrafico elemento = iterador.next();
            if (elementosABajar.contiene(elemento) && elementoDebajo != null) {
                iterador.set(elementoDebajo);
                iterador.previous();
                iterador.previous();
                iterador.set(elemento);
                iterador.next();
                iterador.next();
            } else {
                elementoDebajo = elemento;
            }
        }
    }

    public void borrar(ElementoGrafico elemento) {
        lista.remove(elemento);
    }

    public void borrar(ConjuntoElementos elementos) {
        lista.removeAll(elementos.getLista());
    }

    public void borrar(List<ElementoGrafico> elementos) {
        lista.removeAll(elementos);
    }

    public void desplazar(double dx, double dy) {
        for (ElementoGrafico elemento : lista) {
            elemento.getTransformacion().desplazar(dx, dy);
        }
        actualizarContorno();
    }

    public void escalar(Rectangle2D encuadreInicial, Rectangle2D encuadreFinal) {
        for (ElementoGrafico elemento : lista) {
            elemento.getTransformacion().escalar(encuadreInicial, encuadreFinal);
        }
        actualizarContorno();
    }
    
    public void rotar(Angulo anguloRotacion, double x, double y) {
         for (ElementoGrafico elemento : lista) {
            elemento.getTransformacion().rotar(anguloRotacion, x, y);
         }
         actualizarContorno();
    }

    public void transformar(final Transformacion transformacion) {
        for (ElementoGrafico elemento : lista) {
            elemento.transformar(transformacion);
        }
        actualizarContorno();
    }
}
